import OSS from 'ali-oss';
import { readdirSync, statSync, rmdirSync, unlinkSync } from 'fs';
import { IApi } from 'umi-types';
import 'babel-polyfill';

/**
 * oss相关配置
 */
interface IOSSOptions {
  accessKeyId: string;
  accessKeySecret: string;
  region: string;
  bucket: string;
  secure: boolean;
}

/**
 * 插件相关配置
 */
interface IOptions {
  /** oss配置 */
  ossConfig?: IOSSOptions;
  /** 上传路径 */
  uploadPath?: string;
  /** cdn前缀地址 */
  cdnPrefix?: string;
  /** 不上传html文件 */
  ignoreHtml?: boolean;
  /** 只上传正则匹配的文件 */
  include?: RegExp;
  /** 排除正则匹配的文件 */
  exclude?: RegExp;
  /** 删除上传成功的文件 */
  removeFinished?: boolean;
  /** 保留文件正则匹配，仅在删除时生效 */
  keepFile?: RegExp;
}

export const wait = async (seconds: number = 0): Promise<any> => seconds && new Promise(resolve => setTimeout(resolve, seconds * 1000));

const defaultOptions: IOptions = {
  ossConfig: {
    accessKeyId: '',
    accessKeySecret: '',
    region: '',
    bucket: '',
    secure: true
  },
  exclude: /.DS_Store/,
  ignoreHtml: false,
  removeFinished: false,
  keepFile: /\/*.html/,
};

/**
 * 上传的文件
 */
let uploadFiles: any[] = [];

export default function(api: IApi, opts: IOptions = {}) {
  const { absOutputPath } = api.paths;
  const options = { ...defaultOptions, ...opts };

  const { ossConfig: defaultOssConfig } = defaultOptions;
  const { ossConfig: newOssConfig } = opts;

  const { uploadPath, cdnPrefix, removeFinished, keepFile } = options;

  const ossConfig = { ...defaultOssConfig, ...newOssConfig };

  api.onStart(() => {
  });

  api.onBuildSuccess(() => {
    api.log.note(`🤗 应用构建完成\n`);

    const { accessKeyId, accessKeySecret } = ossConfig;

    if (!accessKeyId) {
      return api.log.error('accessKeyId参数不能为空\n');
    }

    if (!accessKeySecret) {
      return api.log.error('accessKeySecret参数不能为空\n');
    }

    if (!uploadPath) {
      return api.log.error('uploadPath参数不能为空\n');
    }

    readDirSync(absOutputPath);

    api.log.info(`⏰ 待上传文件总数：${uploadFiles.length}\n`);

    if (uploadFiles.length === 0) {
      return api.log.error('没有需要上传的文件\n');
    }

    (async function() {
      try {
        const res = await upload(uploadFiles);
        api.log.log('');
        api.log.success(`🎉 上传文件耗时： ${res / 1000}s\n`);
        api.log.success(`🎉 已上传文件数： ${uploadFiles.length}\n`);
        removeFinished && removeDirSync(absOutputPath);
        removeFinished && api.log.success(`🎉 已自动删除上传成功文件\n`);
      } catch (e) {
        return api.log.error(`${e}\n`);
      }
    })();
  });

  /**
   * 上传文件至OSS
   * @param uploadFiles 
   */
  const upload = async (uploadFiles: any[]): Promise<number> => {
    // 实例化oss客户端
    const ossClient = new OSS(ossConfig);
    const globalStartTime = Date.now();
    for (const file of uploadFiles) {
      const result = await ossClient.put(`${file}`.replace(absOutputPath, uploadPath), file);
      const { name, url } = result;
      if(cdnPrefix) {
        api.log.complete(`上传成功 => ${cdnPrefix}${name}`);
      } else {
        api.log.complete(`上传成功 => ${url}`);
      }
    }
    return new Promise(resolve => resolve(Date.now() - globalStartTime));
  }

  /**
   * 读取需要上传文件集合
   * @param path
   */
  const readDirSync = (path: string) => {
    const allFiles = readdirSync(path);
    allFiles.forEach((item: string) => {
      const filePath = `${path}/${item}`;
      const info = statSync(filePath);
      if (info.isDirectory()) {
        readDirSync(filePath);
      } else {
        if (test(filePath)) {
          uploadFiles.push(filePath);
        }
      }
    });
  };

  /**
   * 删除目录
   * @param path 
   */
  const removeDirSync = (path: string) => {
    // 读取文件夹中所有文件及文件夹
    const fileList = readdirSync(path);
    fileList.forEach((item: string) => {
      // 拼接路径
      const filePath = `${path}/${item}`;
      // 读取文件信息
      const stats = statSync(filePath);
      // 判断是文件还是文件夹
      if (stats.isFile()) {
        if(!keepFile.test(filePath)){
          unlinkSync(filePath);
        }
      } else {
        removeDirSync(filePath);
        rmdirSync(filePath);
      }
    })
  }

  /**
   * 匹配需要上传的文件
   * @param str
   * @param options
   */
  const test = (str: string) => {
    let { include, exclude } = options;
    const { ignoreHtml } = options;

    if(ignoreHtml){
      exclude = /\/*.html/;
    }

    if (exclude && exclude.test(str)) {
      return false;
    }

    if (include) {
      return include.test(str);
    }

    return true;
  };

  api.addUIPlugin(require.resolve('../dist/index.umd'));
}
